

#ifndef quantlib_math_randomnumbers_gpurandomnumbers_hpp
#define quantlib_math_randomnumbers_gpurandomnumbers_hpp

#include <cuda.h>
#include <curand.h>
#include <ql/math/randomnumbers/rngtraits.hpp>

namespace QuantLib 
{

	// Base class - needed for gpumontecarlo
	class GPURandomNumbers {
		public:
			GPURandomNumbers()
				: _sampleSize(0) {
					cudaMalloc((void **)&deviceData, sizeof(float)); // initialise the deviceData with one element - gets overwritten on generate
			}

			virtual void generateSample(int sampleSize) = 0;

			virtual void resetGenerator() = 0;

			virtual ~GPURandomNumbers();

			//virtual void destroySample() {}


			float viewRandomNumber(int index) {
				if (index > _sampleSize)
					return -1.0;
				float *tempRandoms = new float[index+1];
				cudaMemcpy(tempRandoms, deviceData, (index+1) * sizeof(float), cudaMemcpyDeviceToHost);
				float val = tempRandoms[index];
				delete tempRandoms;
				return val;
			}

			int getSampleSize() {
				return _sampleSize;
			}

			float* deviceData; // pointer to the random numbers on device

		protected:
			void assignMemory();
			void destroyAssignedMemory();
			int _sampleSize;
	};

	// Base class which creates and stores a bunch of random numbers directly on the device
	// Mainly used just for exception handling
	class GPURandomNumbersCreatedOnDevice : public GPURandomNumbers {
		public:
			GPURandomNumbersCreatedOnDevice(int seed = 42) : _seed(seed) {};

			void changeSeed(int seed) {
				_seed = seed;
			}

		protected:
			curandGenerator_t _gen;
			int _seed;
	};

	// Class to allow for a bunch of random numbers to be generated upfront and then copied to the device
	// Template argument GSG should be a Gaussian deviate generator which possesses the following attributes:
	//	dimension()
	//	nextSequence()
	//	sample_type
	template<typename GSG = PseudoRandom::rsg_type>
	class GPURandomNumbersCreatedOnHost : public GPURandomNumbers {
		public:
			GPURandomNumbersCreatedOnHost(GSG generator, int sampleSize)
				: _generator(generator)
				{};
	
			void GPURandomNumbersCreatedOnHost::generateSample(int sampleSize) {
				:_sampleSize = sampleSize;
				_numberSets = (sampleSize - 1 + generator.dimension()) / generator.dimension();
				float *hostData = new float(_sampleSize);
				size_t idx = 0;
				for (int i=0; i<_numberSets; ++i)
				{
					GSG::sample_type s = _generator.nextSequence();
					while (idx < _sampleSize) {
						hostData[idx] = s.value[idx];
						++idx;
					}	
				}
				cudaMemcpy(hostData, deviceData, _sampleSize * sizeof(float), cudaMemcpyHostToDevice);
				delete hostData;
			}

		protected:
			int _numberSets; // number of times to call _generator
			GSG _generator;
	};

}

#endif